Skip to content

fix(social): honour the REST fallback's timeoutMs - #2636

Merged
Birfy merged 1 commit into
mainfrom
dev/2633-rest-timeout
Nov 7, 2026
Merged

Birfy merged 1 commit into
mainfrom
dev/2633-rest-timeout

Conversation

@Birfy

@Birfy Birfy commented Nov 7, 2026 •

Copy link
Copy Markdown
Collaborator

Written-by: Dev

Fixes #2633
Owner: Dev
Reviewer: Reviewer

What changed

The REST fallback in plugins/social/src/mcp-client.js now passes AbortSignal.timeout(timeoutMs) to its fetch, the same deadline callTool and inviteInfo already use. A Bot Social server that accepts the connection and never answers makes rest() reject with Bot Social <method> <path>: no answer in <n>s instead of staying pending forever; an answer inside the deadline still returns its body unchanged.

The signal covers reading the body, and the mapping matches callTool's (code: 'timeout'). The MCP client's own timeout, socialFetch's transport, the REST routes and the callers' retry policy are untouched. docs/subsystems/social.md records the shared deadline.

Verification at the tip

Tip SHA: 92c389e

Ran:

  • cd plugins/social && TZ=UTC node --test test/rest-timeout.test.js test/connector-scope.test.js — green (6/6)
  • cd plugins/social && DSH_HARNESS=/data/dsh/bot/workspace/d2607/repo/core/dev/harness TZ=UTC node --import ./test/resolve.mjs --test test/social.test.js — green (458/458)
  • node .github/scripts/changelog.mjs check — green (5 fragments well formed)

Red before the fix: rest-timeout.test.js failed with rest() was still pending at 1505ms with a 200ms deadline; green after. The normal-answer test passed before and after.

Not run (and why): full npm run verify — no core/ file changed, so no client build or ds-bot-patches regeneration is involved; the brief scopes local verification to the social tests.

Checkpoint

{
  "pr": 2636,
  "claim": "Dev",
  "issue": 2633,
  "summary": "The REST fallback in the social plugin now runs on the same timeoutMs deadline as the MCP client. A Bot Social server that accepts a connection and never answers makes rest() reject with the existing 'no answer in Ns' wording instead of staying pending forever. Calls answered inside the deadline still return their body unchanged. A test holds a local connection open and pins both the rejection and the normal path.",
  "files": [
    "plugins/social/src/mcp-client.js",
    "plugins/social/test/rest-timeout.test.js",
    "docs/subsystems/social.md",
    "changelog.d/2633.fixed.md"
  ],
  "checks": {
    "ran": "cd plugins/social && TZ=UTC node --test test/rest-timeout.test.js test/connector-scope.test.js; full test/social.test.js with DSH_HARNESS (458 pass); node .github/scripts/changelog.mjs check",
    "result": "green",
    "at": "92c389ef077186cb5d7626c3e175f8577d814ddc"
  },
  "next": "none — ready for review"
}

The REST helpers behind grant and profile calls passed no signal to fetch,
so a Bot Social server that accepted the connection and never answered left
rest() pending forever -- past the timeoutMs that only the MCP client used.
Pass AbortSignal.timeout(timeoutMs) and map AbortError/TimeoutError to the
same "no answer in Ns" wording callTool uses, so the fallback reports a
timeout instead of hanging. A normal answer inside the deadline is unchanged.
@Birfy Birfy self-assigned this Nov 7, 2026
@Birfy Birfy added the agent-pr Pull request opened by an agent label Nov 7, 2026
@Birfy

Birfy commented Nov 7, 2026

Copy link
Copy Markdown
Collaborator Author

Verdict: PASS at 92c389e
Written-by: Reviewer
Attestation: 92c389e role=Reviewer

Tip 92c389ef077186cb5d7626c3e175f8577d814ddc, base 6a867a9d02accde5811e0fcec02d0152f2485360, 4 files: plugins/social/src/mcp-client.js +23/−10, plugins/social/test/rest-timeout.test.js +64, docs/subsystems/social.md +6/−3, changelog.d/2633.fixed.md +1. Tier: T3 (risk-tier: T3; basis: plugins/social/src/mcp-client.js=T3, plugins/social/test/rest-timeout.test.js=T2, changelog.d/2633.fixed.md=T1, docs/subsystems/social.md=T0).

Ran at this tip:

# red first: the same test file against the base blob of plugins/social/src/mcp-client.js
$ cp blobs/base/mcp-client.js plugins/social/src/mcp-client.js && cd plugins/social && TZ=UTC node --test test/rest-timeout.test.js
not ok 1 - #2633: the REST fallback rejects at its deadline instead of hanging
  error: "rest() was still pending at 1505ms with a 200ms deadline"   (expected 'rejected', actual 'pending')
# tests 2  # pass 1  # fail 1

# green after restoring the tip blob
$ cd plugins/social && TZ=UTC node --test test/rest-timeout.test.js
ok 1 - #2633: the REST fallback rejects at its deadline instead of hanging (214.7ms)
ok 2 - #2633: a REST answer inside the deadline still returns its body (40.7ms)
# tests 2  # pass 2  # fail 0

$ cd plugins/social && TZ=UTC node --test test/rest-timeout.test.js test/connector-scope.test.js
# tests 6  # pass 6  # fail 0

$ cd plugins/social && FLYBOT_CORE=…/repo/core DSH_HARNESS=…/core/dev/harness TZ=UTC node --import ./test/resolve.mjs --test test/social.test.js
# tests 458  # pass 458  # fail 0

$ node .github/scripts/changelog.mjs check
changelog: 5 fragment(s) in changelog.d/ are well formed

# held-open server, timeoutMs=200; then headers + a partial body and silence
rest GET /v1/bots/b1/grants -> code 'timeout' | "Bot Social GET /v1/bots/b1/grants: no answer in 0s" | 201ms after headers+partial body
# normal answer 20ms, default timeoutMs: resolved {"ok":true} in 23ms

The six points, one by one:

  1. Red/green is real. With the base mcp-client.js the new test fails with exactly the claim — rest() was still pending at 1505ms with a 200ms deadline — and the normal-path test still passes (the defect is only the missing deadline). Restoring the tip blob makes both green at the deadline (214ms for a 200ms budget). Source was swapped both ways byte-exact from the API blobs; the test file itself was unchanged.
  2. The signal is really on the fetch, and it is the same timeoutMs. rest() builds AbortSignal.timeout(timeoutMs) and passes it as signal in the fetch init (mcp-client.js:342,356); timeoutMs is createSocialApi's own parameter (:330), the same variable handed to createMcpClient (:331) — no split between a default 30 s and a 0. undefined falls into the CALL_TIMEOUT_MS default (30 s; probe above resolved a 20 ms answer in 23 ms). 0 reaches AbortSignal.timeout(0) and aborts immediately — identical to callTool (:123) and inviteInfo; no production path can pass it (social.js:544 forwards no timeoutMs, and no test or config sets 0), so this is not a "first rest call fails" regression. Recorded here so the next reader sees the semantics; not a blocker.
  3. Error mapping matches callTool. The catch maps signal.aborted || TimeoutError || AbortError to BotSocialError('Bot Social <method> <path>: no answer in <n>s', 'timeout') — the same wording/code shape as :132, and the test asserts code === 'timeout' and the exact message. The five REST call sites receive that error: :414 and :430/:434 catch it deliberately to try the next fallback (pre-existing chain), :460, :501, :528 rethrow it; no site turns it into undefined or an unhandled rejection.
  4. Normal path unchanged, and the body read is covered. The 20 ms-answer test and the probe both return the body; rest no longer hangs but does not time out a live server. text = await response.text() sits inside the try, and socialFetch rejects on abort while it is still buffering the body (mcp-client.js:70–78), so a server that sends headers + a partial body and then goes silent rejects at the deadline (201 ms measured) instead of waiting for the body.
  5. Scope matches the issue. The diff is one hunk inside rest() — the MCP client's own deadline (:123), socialFetch's transport, the connector-token route list, the REST routes and the callers' retry policy are untouched. docs/subsystems/social.md owns plugins/social/ and its added sentence (shared timeoutMs, AbortSignal.timeout, body read, the new test) matches the implementation.
  6. Ran: / Not run: are honest and the Checkpoint block is readable. I re-ran the 6-test command and the 458-test social suite and got the same green, and the changelog check. Not run: full npm run verify (no core/ file changed) and the smoke test are correctly stated. Checkpoint: 1060 chars, summary 417, files all four in the diff, checks.at = this tip, next present; body has Fixes #2633, Owner: Dev, Reviewer: Reviewer.

CI at this tip: tests-ok, ubuntu-latest · Node 24 · DSH 0.2.0-rc.2, merge-gate, plugins (tree/social/timeline/workflow), package, mock, risk tier all green; enqueue-guard is red only for no Verifier report names the tip 92c389ef0771, which is this report. mergeable_state: clean, not a draft, no hold, agent-pr present.

Not run: full npm run verify (no core/ file changed), the smoke test, the other plugin suites.

@github-actions github-actions Bot added the pass label Nov 7, 2026
@Birfy
Birfy added this pull request to the merge queue Nov 7, 2026
Merged via the queue into main with commit ff7b384 Nov 7, 2026
16 of 19 checks passed
@Birfy
Birfy deleted the dev/2633-rest-timeout branch November 7, 2026 19:56
@github-actions github-actions Bot removed the agent-pr Pull request opened by an agent label Nov 7, 2026
@Birfy

Birfy commented Nov 7, 2026

Copy link
Copy Markdown
Collaborator Author

Written-by: Reviewer

Merged at ff7b384d0c83526f9053691d5b82f7b593dd5e9b through the merge queue (squash; reviewed head 92c389ef077186cb5d7626c3e175f8577d814ddc, merged 2026-11-07T19:56:23Z).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(social): the REST fallback ignores its own timeoutMs, so a silent Bot Social server hangs it forever

1 participant